package com.zhkj.crawl.service.impl;

import com.zhkj.crawl.config.Config;
import com.zhkj.crawl.config.Constants;
import com.zhkj.crawl.utils.ChromeMethodExt;
import com.zhkj.crawl.utils.UserAgentGen;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverLogLevel;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.io.File;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * chromeDriver直接获取page Document
 */
@Slf4j
public class ChromeService {
    // 和线程绑定
    private static final Map<String, ChromeDriver> DRIVER_MAP = new HashMap<>();
    private static final String DEFAULT_WAIT_ELEMENT = "body";
    private static final int DEFAULT_WAIT = 10;


    static {
        Logger.getLogger("org.openqa.selenium").setLevel(Level.OFF);
        System.setProperty("webdriver.chrome.driver", "/usr/local/bin/chromedriver101");

        if (Constants.isWindows) {
            System.setProperty("webdriver.chrome.driver", "D:\\Program Files\\chromedriver_91_0_4472_101.exe");
        }
    }


    public static Document go(ChromeDriver driver, String url, int timeoutSec, String untilCssSelector){
        driver.get(url);
        return wait4Dom(driver, timeoutSec, untilCssSelector);
    }

    private static Document wait4Dom(ChromeDriver driver, int timeoutSec, String untilCssSelector){
        WebDriverWait webDriverWait = new WebDriverWait(driver, Duration.ofSeconds(timeoutSec));
        if(StringUtils.isNotBlank(untilCssSelector)){
            String splitStr = "@@";
            if(untilCssSelector.contains(splitStr)){
                String[] split = untilCssSelector.split(splitStr);
                ExpectedCondition<WebElement>[] arr = new ExpectedCondition[split.length];
                for (int i = 0; i < split.length; i++) {
                    arr[i] = ExpectedConditions.presenceOfElementLocated(By.cssSelector(split[i]));
                }
                webDriverWait.until(ChromeMethodExt.or(arr));
            }else {
                webDriverWait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(untilCssSelector)));
            }
        }else {
            driver.manage().timeouts().implicitlyWait(timeoutSec, TimeUnit.SECONDS);
//            webDriverWait.until(ExpectedConditions.javaScriptThrowsNoExceptions("return jQuery.active == 0"));
        }

        driver.executeScript("window.stop()");
        WebElement webElement = driver.findElement(By.xpath("/html"));
        String responseContent = webElement.getAttribute("outerHTML");

        return Jsoup.parse(responseContent);
    }

    public static Document go(String url){
        ChromeDriver driver = getCurrentDriver();
        return go(driver, url, DEFAULT_WAIT, null);
    }

    public static Document go(String url, int timeoutSec){
        ChromeDriver driver = getCurrentDriver();
        return go(driver, url, timeoutSec, null);
    }

    public static Document go(String url, String cssSelector){
        ChromeDriver driver = getCurrentDriver();
        return go(driver, url, DEFAULT_WAIT, cssSelector);
    }

    public static Document go(String url, int timeoutSec, String cssSelector){
        ChromeDriver driver = getCurrentDriver();
        return go(driver, url, timeoutSec, cssSelector);
    }

    public static void reset(){
        ChromeDriver driver = getCurrentDriver();
        driver.manage().deleteAllCookies();
    }


    private static ChromeDriver getCurrentDriverIfExist(){
        String threadName = Thread.currentThread().getName();
        return DRIVER_MAP.get(threadName);
    }

    private static ChromeDriver getCurrentDriver(){
        String threadName = Thread.currentThread().getName();
        ChromeDriver driver = DRIVER_MAP.get(threadName);
        if(driver == null){
            driver = initDriver();
            DRIVER_MAP.put(threadName, driver);
        }
        return driver;
    }


    private static synchronized ChromeDriver initDriver() {
        String threadName = Thread.currentThread().getName();
        ChromeDriver driver = DRIVER_MAP.get(threadName);
        if(driver != null){
            return driver;
        }

        ChromeOptions chromeOptions = new ChromeOptions();
        chromeOptions.setLogLevel(ChromeDriverLogLevel.OFF);
        chromeOptions.addArguments("--no-sandbox");
        chromeOptions.addArguments("--disable-dev-shm-usage"); 
        chromeOptions.addArguments("--window-size=1920x1080");
        chromeOptions.addArguments("--start-maximized");
        chromeOptions.addArguments("--silent");
        chromeOptions.addArguments("log-level=3");
        chromeOptions.addArguments("--disable-blink-features=AutomationControlled");
        chromeOptions.addArguments("--user-agent=" + UserAgentGen.gen());
        if(!Constants.isWindows){
            chromeOptions.addArguments("--headless");
//            chromeOptions.addArguments(String.format("load-extension=%s/src/main/resources/proxyExtension", System.getProperty("user.dir")));
        }
        chromeOptions.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation"));

        if(Config.proxySwitch){
            Proxy proxy = new Proxy();
            String proxyAddr = String.format("%s:%s", Constants.PROXY_HOST, Constants.PROXY_PORT);
            proxy.setHttpProxy(proxyAddr)
                    .setFtpProxy(proxyAddr)
                    .setSslProxy(proxyAddr);
            chromeOptions.setProxy(proxy);
        }
        // 禁用图片 & 样式 & flash
        Map<String, Object> prefs = new HashMap<>();
        prefs.put("profile.managed_default_content_settings.images", 2);
        prefs.put("profile.managed_default_content_settings.stylesheet", 2);
        prefs.put("dom.ipc.plugins.enabled.libflashplayer.so", "false");
        chromeOptions.setExperimentalOption("prefs", prefs);

        ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("source", " Object.defineProperty(navigator, 'webdriver', {\n" +
                "      get: () => undefined\n" +
                "    })");
        chromeDriver.executeCdpCommand("Page.addScriptToEvaluateOnNewDocument", parameters);
        if(Constants.isWindows){
            chromeDriver.manage().window().minimize();
//            chromeDriver.manage().window().setPosition(new Point(0, -2000));
        }

        log.info("初始化chromeDriver");
        return chromeDriver;
    }

    /**
     * 结束 释放所有driver
     */
    public static void releaseAll(){
        for (ChromeDriver value : DRIVER_MAP.values()) {
            if(value != null){
                try { value.quit(); }catch (Exception ignore){ }
            }
        }
    }

    /**
     * input填充内容
     * @param id
     * @param result
     */
    public static void fillIptById(String id, String result){
        ChromeDriver driver = getCurrentDriver();
        driver.findElementById(id).sendKeys(result);
    }


    /**
     * 点击跳转并返回dom
     * @param xpath
     * @return
     */
    public static Document jumpByXpath(String xpath){
        ChromeDriver driver = getCurrentDriver();
        driver.findElement(By.xpath(xpath)).click();
        return wait4Dom(driver, DEFAULT_WAIT, DEFAULT_WAIT_ELEMENT);
    }

    public static Document jumpByCss(String cssSelector) {
        ChromeDriver driver = getCurrentDriver();
        driver.findElement(By.cssSelector(cssSelector)).click();
        return wait4Dom(driver, DEFAULT_WAIT, DEFAULT_WAIT_ELEMENT);
    }

    public static void release() {
        ChromeDriver driver = getCurrentDriver();
        try {
            driver.quit();
            DRIVER_MAP.remove(Thread.currentThread().getName());
            driver = null;
            log.info("driver quit...");
        }catch (Exception ignore){
        }
    }


    public static String snapshot() {
        ChromeDriver driver = getCurrentDriverIfExist();
        if(driver == null){
            return null;
        }

        try {
            File scrFile = driver.getScreenshotAs(OutputType.FILE);
            String filePath = Config.snapshotPath;
            String fileName = System.currentTimeMillis() + ".jpg";
            File destFile = new File(filePath + File.separator + fileName);
            FileUtils.copyFile(scrFile, destFile);
            return destFile.getAbsolutePath();
        }catch (Exception e){
            log.error("截图错误", e);
        }
        return null;
    }

    public static void clickByCss(String cssSelector) {
        ChromeDriver driver = getCurrentDriver();
        driver.findElement(By.cssSelector(cssSelector)).click();
    }

}
